{
 "cells": [
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## 打造一个简单聊天机器人\n",
    "具有的功能如下：\n",
    "1、持久化记忆\n",
    "2、工具调用\n",
    "3、查询知识库\n",
    "4、知识库上传（仅仅纯文本）"
   ],
   "id": "44ef9f6b38ea3bd1"
  },
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-07-31T13:12:19.211482Z",
     "start_time": "2025-07-31T13:12:13.685570Z"
    }
   },
   "source": [
    "import operator\n",
    "import os\n",
    "from typing import Annotated, List\n",
    "\n",
    "from dotenv import load_dotenv\n",
    "from langchain_core.messages import BaseMessage\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_openai import ChatOpenAI\n",
    "from pydantic import BaseModel, Field\n",
    "\n",
    "\n",
    "class SimpleState(BaseModel):\n",
    "    \"\"\"包含用户请求和历史对话的简单状态\"\"\"\n",
    "    query: str\n",
    "    intent: str=\"\"\n",
    "    chat_history: Annotated[List[BaseMessage], operator.add]\n",
    "    log: Annotated[List[str], operator.add]  #累计的日志\n",
    "\n",
    "\n",
    "# 加载环境变量\n",
    "load_dotenv()\n",
    "llm = ChatOpenAI(\n",
    "    api_key=os.getenv(\"DASHSCOPE_KEY\"),\n",
    "    base_url=os.getenv(\"DASHSCOPE_OPENAI_URL\"),\n",
    "    model=\"qwen-plus-1220\"\n",
    ")\n",
    "\n",
    "\n",
    "def initial_state(state: SimpleState):\n",
    "    \"\"\"初始化全局状态\"\"\"\n",
    "    log_entry = \"------初始化状态------\"\n",
    "    print(log_entry)\n",
    "    return {\n",
    "        \"intent\": \"\",\n",
    "        \"chat_history\": [],\n",
    "        \"log\": [log_entry]\n",
    "    }\n",
    "\n",
    "\n",
    "def triage_node(state: SimpleState):\n",
    "    \"\"\"意图识别节点\"\"\"\n",
    "    # 定义提示模板\n",
    "    prompt_template = ChatPromptTemplate.from_messages([\n",
    "        (\"system\", \"你是一个意图识别助手。根据用户的输入，判断意图并返回以下之一，注意只能是其中一个英文单词：\"\n",
    "                   \"- 'chat'：用户进行的是普通聊天。\"\n",
    "                   \"- 'knowledge'：用户询问的是Java相关问题。\"),\n",
    "        (\"human\", \"{query}\")\n",
    "    ])\n",
    "    # 创建链式处理逻辑\n",
    "    chain = prompt_template | llm | StrOutputParser()\n",
    "    # 获取用户输入并调用链\n",
    "    intent = chain.invoke({\"query\": state.query})\n",
    "\n",
    "    # 打印日志\n",
    "    log_entry = f\"--- 意图识别结果: {intent} ---\"\n",
    "    print(log_entry)\n",
    "\n",
    "    # 返回路由决策\n",
    "    return {\n",
    "        \"intent\": intent,\n",
    "        \"log\": [log_entry]\n",
    "    }\n",
    "\n",
    "\n",
    "def decide_after_triage(state: SimpleState):\n",
    "    \"\"\"意图识别之后，决定跳转到哪个节点\"\"\"\n",
    "    if state.intent == \"chat\":\n",
    "        return \"chat\"\n",
    "    else:\n",
    "        return \"knowledge\"\n"
   ],
   "outputs": [],
   "execution_count": 3
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "### 定义简单工具",
   "id": "49e55e953e71039e"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-31T13:12:19.267329Z",
     "start_time": "2025-07-31T13:12:19.227046Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from datetime import datetime\n",
    "import random\n",
    "from langchain_core.tools import StructuredTool\n",
    "from typing import Optional\n",
    "\n",
    "\n",
    "def get_current_date() -> str:\n",
    "    \"\"\"\n",
    "    获取当前日期\n",
    "    Returns:\n",
    "        str: 格式化的当前日期字符串\n",
    "    \"\"\"\n",
    "    today_time = datetime.now()\n",
    "    return today_time.strftime(\"%Y年%m月%d日 %A\")\n",
    "\n",
    "\n",
    "def get_current_weather(location: str = \"北京\") -> dict:\n",
    "    \"\"\"\n",
    "    获取指定位置的当前天气信息（模拟数据）\n",
    "    Args:\n",
    "        location (str): 位置名称，默认为\"北京\"\n",
    "    Returns:\n",
    "        dict: 包含天气信息的字典\n",
    "    \"\"\"\n",
    "    # 模拟天气数据\n",
    "    weather_conditions = [\"晴天\", \"多云\", \"阴天\", \"小雨\", \"大雨\", \"雪\", \"雾\"]\n",
    "    wind_directions = [\"北风\", \"南风\", \"东风\", \"西风\", \"东北风\", \"西北风\", \"东南风\", \"西南风\"]\n",
    "\n",
    "    # 随机生成天气数据\n",
    "    temperature = random.randint(-10, 35)  # 随机温度\n",
    "    condition = random.choice(weather_conditions)\n",
    "    humidity = random.randint(30, 90)  # 随机湿度\n",
    "    wind_speed = random.randint(0, 20)  # 随机风速\n",
    "    wind_direction = random.choice(wind_directions)\n",
    "\n",
    "    return {\n",
    "        \"location\": location,\n",
    "        \"temperature\": temperature,\n",
    "        \"condition\": condition,\n",
    "        \"humidity\": humidity,\n",
    "        \"wind_speed\": wind_speed,\n",
    "        \"wind_direction\": wind_direction,\n",
    "        \"description\": f\"{location}当前天气为{condition}，温度{temperature}°C，湿度{humidity}%，{wind_direction}{wind_speed}km/h\"\n",
    "    }\n",
    "\n",
    "\n",
    "# 定义输入模型\n",
    "class DateInput(BaseModel):\n",
    "    pass\n",
    "\n",
    "\n",
    "class WeatherInput(BaseModel):\n",
    "    location: Optional[str] = Field(default=\"北京\", description=\"城市名称\")\n",
    "\n",
    "\n",
    "# 使用 StructuredTool 创建工具\n",
    "date_tool = StructuredTool.from_function(\n",
    "    func=get_current_date,\n",
    "    name=\"get_current_date\",\n",
    "    description=\"获取当前日期信息\",\n",
    "    args_schema=DateInput\n",
    ")\n",
    "\n",
    "weather_tool = StructuredTool.from_function(\n",
    "    func=get_current_weather,\n",
    "    name=\"get_current_weather\",\n",
    "    description=\"获取指定位置的天气信息\",\n",
    "    args_schema=WeatherInput\n",
    ")"
   ],
   "id": "6c591b1d22c9cfe3",
   "outputs": [],
   "execution_count": 4
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "### 定义知识库检索工具",
   "id": "844ab1f75053f330"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-31T13:12:19.292057Z",
     "start_time": "2025-07-31T13:12:19.282560Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import numpy as np\n",
    "import requests\n",
    "from config.database import db\n",
    "\n",
    "\n",
    "# 计算余弦相似度\n",
    "def cosine_similarity(vec1, vec2):\n",
    "    return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))\n",
    "\n",
    "\n",
    "# 获取 pgvector 嵌入\n",
    "def get_ollama_embedding(text: str) -> list[float]:\n",
    "    payload = {\n",
    "        \"model\": \"dengcao/Qwen3-Embedding-0.6B:Q8_0\",\n",
    "        \"prompt\": text\n",
    "    }\n",
    "    response = requests.post(\"http://localhost:11434/api/embeddings\", json=payload)\n",
    "    if response.status_code == 200:\n",
    "        embedding = response.json()['embedding']\n",
    "        return embedding\n",
    "    else:\n",
    "        raise Exception(f\"Failed to get embedding: {response.text}\")\n",
    "\n",
    "\n",
    "import re\n",
    "\n",
    "\n",
    "# 解析 pgvector 返回的向量字符串\n",
    "def parse_embedding(embedding_str: str) -> np.ndarray:\n",
    "    \"\"\"\n",
    "    将 pgvector 返回的字符串格式的向量转换为 numpy 数组\n",
    "    示例输入: '[ -3.5594618, 2.123456, 0.123456 ]'\n",
    "    输出: numpy.ndarray([ -3.5594618, 2.123456, 0.123456 ])\n",
    "    \"\"\"\n",
    "    # 去除方括号和空格\n",
    "    clean_str = re.sub(r'[\\[\\]\\s]+', '', embedding_str)\n",
    "    # 分割成浮点数字符串列表\n",
    "    float_strs = clean_str.split(',')\n",
    "    # 转换为浮点数列表\n",
    "    float_list = [float(x) for x in float_strs if x]\n",
    "    # 转换为 numpy 数组（便于计算）\n",
    "    return np.array(float_list, dtype=np.float32)\n",
    "\n",
    "\n",
    "# 检索 知识库\n",
    "def retrieve_knowledge(question: str, threshold=0.75):\n",
    "    embedding = get_ollama_embedding(question)\n",
    "    conn = db.getconn()\n",
    "    cur = conn.cursor()\n",
    "\n",
    "    cur.execute(\"SELECT question, answer, embedding FROM chatbot.public.knowledge\")\n",
    "    rows = cur.fetchall()\n",
    "    best_match = None\n",
    "    best_score = -1\n",
    "\n",
    "    for row in rows:\n",
    "        q, a, db_embedding = row\n",
    "        db_embedding = parse_embedding(db_embedding)  # 关键修改：转换为 float 数组\n",
    "        score = cosine_similarity(embedding, db_embedding)\n",
    "        if score > best_score and score >= threshold:\n",
    "            best_score = score\n",
    "            best_match = a  # 返回答案\n",
    "\n",
    "    cur.close()\n",
    "    db.putconn(conn)\n",
    "\n",
    "    return best_match\n",
    "\n",
    "\n",
    "class KnowledgeInput(BaseModel):\n",
    "    query: Optional[str] = Field(default=\"JAVA最主流的脚手架是什么\", description=\"用户提问\")\n",
    "\n",
    "\n",
    "# 使用 StructuredTool 创建工具\n",
    "retrieve_tool = StructuredTool.from_function(\n",
    "    func=retrieve_knowledge,\n",
    "    name=\"retrieve_knowledge\",\n",
    "    description=\"知识库检索\",\n",
    "    args_schema=KnowledgeInput\n",
    ")"
   ],
   "id": "617e784003ad17fa",
   "outputs": [],
   "execution_count": 5
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "### 简单对话智能体、知识查询智能体的创建",
   "id": "c647a3b8e6c88697"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-31T13:12:19.673522Z",
     "start_time": "2025-07-31T13:12:19.315008Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from langgraph.prebuilt import create_react_agent\n",
    "from langgraph.prebuilt.chat_agent_executor import AgentState\n",
    "from langchain_core.messages import AIMessage, HumanMessage\n",
    "\n",
    "\n",
    "class SimpleAgentState(AgentState):\n",
    "    # 可以自行拓展\n",
    "    pass\n",
    "\n",
    "\n",
    "def chat_node(state: SimpleState):\n",
    "    \"\"\"简单聊天智能体\"\"\"\n",
    "    chat_agent = create_react_agent(model=llm, tools=[date_tool, weather_tool], debug=True,\n",
    "                                    state_schema=SimpleAgentState)\n",
    "    prompt = ChatPromptTemplate.from_template(\"\"\"\n",
    "        你是一个活泼开朗的聊天机器人，根据用户的消息做出相应的回答\n",
    "        ### 历史对话 ###\n",
    "        {chat_history}\n",
    "\n",
    "        ### 当前消息 ###\n",
    "        {query}\n",
    "\n",
    "        你可以调用相关工具来完成任务\n",
    "        \"\"\")\n",
    "    message = prompt.format(chat_history=state.chat_history, query=state.query)\n",
    "    agent_res = chat_agent.invoke({\n",
    "        \"messages\": message,\n",
    "    })\n",
    "    log_entry = f\"--- 意图识别结果: {agent_res} ---\"\n",
    "    print(log_entry)\n",
    "    return {\n",
    "        \"chat_history\": [HumanMessage(state.query), agent_res[\"messages\"][-1]],\n",
    "        \"log\": [log_entry]\n",
    "    }\n",
    "\n",
    "\n",
    "def retrieve_chat_node(state: SimpleState):\n",
    "    \"\"\"\n",
    "    Java 知识助手节点：根据用户输入调用知识库检索工具，返回最匹配的答案。\n",
    "    \"\"\"\n",
    "    # 定义提示模板\n",
    "    prompt = ChatPromptTemplate.from_template(\"\"\"\n",
    "        你是一个严谨的JAVA开发专家，你需要根据知识库检索到的知识，回答用户的提问\n",
    "        ### 知识库检索 ###\n",
    "        {knowledge_answer}\n",
    "\n",
    "        ### 历史消息 ###\n",
    "        {chat_history}\n",
    "        ### 当前消息 ###\n",
    "        {query}\n",
    "        \"\"\")\n",
    "\n",
    "    # 调用知识库检索工具\n",
    "    knowledge_answer = retrieve_knowledge(state.query)\n",
    "\n",
    "    if knowledge_answer:\n",
    "        # 如果检索到答案，生成回复\n",
    "        message = prompt.format(knowledge_answer=knowledge_answer, chat_history=state.chat_history, query=state.query)\n",
    "        response = llm.invoke(message)\n",
    "    else:\n",
    "        # 如果未检索到答案，告知用户\n",
    "        response = AIMessage(\"抱歉，我未能在知识库中找到与您的问题相关的答案。请尝试提供更多细节或稍后再试。\")\n",
    "\n",
    "    # 打印日志\n",
    "    log_entry = f\"--- Java 知识助手回复: {response} ---\"\n",
    "    print(log_entry)\n",
    "\n",
    "    # 返回状态更新\n",
    "    return {\n",
    "        \"chat_history\": [HumanMessage(state.query), response],\n",
    "        \"log\": [log_entry]\n",
    "    }\n"
   ],
   "id": "b4c1e1c16258d33a",
   "outputs": [],
   "execution_count": 6
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "### 构建和编译图",
   "id": "1b4e63d7569dabd"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-31T13:12:19.702144Z",
     "start_time": "2025-07-31T13:12:19.689721Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from langgraph.graph import StateGraph,END\n",
    "\n",
    "workflow = StateGraph(SimpleState)\n",
    "\n",
    "workflow.add_node(\"initializer\", initial_state)\n",
    "workflow.add_node(\"triage_node\", triage_node)\n",
    "workflow.add_node(\"chat_node\", chat_node)\n",
    "workflow.add_node(\"retrieve_chat_node\", retrieve_chat_node)\n",
    "\n",
    "workflow.set_entry_point(\"initializer\")\n",
    "workflow.add_edge(\"initializer\", \"triage_node\")\n",
    "\n",
    "workflow.add_conditional_edges(\"triage_node\", decide_after_triage, {\n",
    "    \"chat\": \"chat_node\",\n",
    "    \"knowledge\": \"retrieve_chat_node\"\n",
    "})\n",
    "workflow.add_edge(\"chat_node\",END)\n",
    "workflow.add_edge(\"retrieve_chat_node\",END)\n",
    "\n",
    "app=workflow.compile()"
   ],
   "id": "fadde62e0124dade",
   "outputs": [],
   "execution_count": 7
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-31T13:12:21.868069Z",
     "start_time": "2025-07-31T13:12:19.719219Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from IPython.display import Image, display\n",
    "\n",
    "display(Image(app.get_graph().draw_mermaid_png()))"
   ],
   "id": "9ee9b5b5fa88c7c3",
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 8
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-31T13:12:31.842457Z",
     "start_time": "2025-07-31T13:12:21.886152Z"
    }
   },
   "cell_type": "code",
   "source": [
    "\n",
    "response = app.invoke({\n",
    "    \"query\": \"Java 中的 final 关键字有什么作用？\",\n",
    "    # \"intent\": \"\",\n",
    "    # \"chat_history\": [],\n",
    "    # \"log\": []\n",
    "})"
   ],
   "id": "a29e71097db12fbe",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "------初始化状态------\n",
      "--- 意图识别结果: knowledge ---\n",
      "--- Java 知识助手回复: content='在 Java 中，`final` 关键字有多种用途，具体可以用于修饰类、方法和变量。以下是 `final` 关键字的详细作用：\\n\\n1. **修饰类**：\\n   - 当一个类被声明为 `final` 时，表示该类不能被继承。即其他类不能从这个类派生出子类。\\n   - 示例：\\n     ```java\\n     final class FinalClass {\\n         // 类内容\\n     }\\n     // 下面的代码会导致编译错误\\n     // class SubClass extends FinalClass { }\\n     ```\\n\\n2. **修饰方法**：\\n   - 当一个方法被声明为 `final` 时，表示该方法不能被重写（override）。即子类不能提供与父类中相同签名的方法。\\n   - 示例：\\n     ```java\\n     class ParentClass {\\n         final void finalMethod() {\\n             // 方法内容\\n         }\\n     }\\n     class ChildClass extends ParentClass {\\n         // 下面的代码会导致编译错误\\n         // void finalMethod() { }\\n     }\\n     ```\\n\\n3. **修饰变量**：\\n   - 当一个变量被声明为 `final` 时，表示该变量的值不能被修改。对于基本数据类型的变量，这意味着一旦赋值就不能再改变；对于引用类型的变量，这意味着引用地址不能改变，但对象的内容可以改变（如果对象本身是可变的）。\\n   - 示例：\\n     ```java\\n     final int finalVariable = 10;\\n     // 下面的代码会导致编译错误\\n     // finalVariable = 20;\\n\\n     final List<String> finalList = new ArrayList<>();\\n     // 引用地址不能改变\\n     // finalList = new ArrayList<>();\\n     // 但是列表中的元素可以改变\\n     finalList.add(\"Hello\");\\n     ```\\n\\n总结来说，`final` 关键字的主要作用是增加代码的安全性和稳定性，防止不必要的继承或方法重写，以及确保变量值的不可变性。' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 417, 'prompt_tokens': 119, 'total_tokens': 536, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'qwen-plus-1220', 'system_fingerprint': None, 'id': 'chatcmpl-666f6e77-b84a-9708-bcb3-fde001ad7405', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None} id='run--369a59c5-f51c-4309-88ae-fa76391ef49c-0' usage_metadata={'input_tokens': 119, 'output_tokens': 417, 'total_tokens': 536, 'input_token_details': {}, 'output_token_details': {}} ---\n"
     ]
    }
   ],
   "execution_count": 9
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-31T13:12:31.905359Z",
     "start_time": "2025-07-31T13:12:31.901359Z"
    }
   },
   "cell_type": "code",
   "source": "",
   "id": "ca0929cb770f2776",
   "outputs": [],
   "execution_count": null
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
